#define _GNU_SOURCE
#include <stdio.h>
#include <assert.h>
#include <math.h>

#include <object_table.h>
#include <circle.h>
#include <line.h>
#include <utils.h>
#include <debug.h>


/**
 * Allocate a new circle.
 *
 * @return the new allocated circle object.
 */
circle_t *
circle_new( void )
{
  circle_t *circle = calloc( 1, sizeof(*circle) );

  OBJECT_TYPE( circle ) = OBJECT_TYPE_CIRCLE;

  return circle;
}

/**
 * Free memory allocated to circle.
 *
 * @param an allocated circle object.
 */
void
circle_free( circle_t *circle )
{
  assert( circle != NULL );
  free( circle );
}

/**
 * Return the string representation of this circle object.
 *
 * @note string is malloc()ated, please free() it later.
 *
 * @param circle the object to act on.
 *
 * @return string representation.
 */
char *
circle_str( circle_t *circle )
{
  char *s = NULL;
  int r;

  if ( circle == NULL )
    r = asprintf( &s, "Circle is NULL!" );
  else
    r = asprintf( &s,
		  "Circle( depth=%d, color=0x%08x, transp=%0.3f, "
		  "center=( %d, %d ), radius=%d )",
		  OBJECT_DEPTH( circle ),
		  COLOR_TO_ARGB( OBJECT_COLOR( circle ) ),
		  OBJECT_TRANSP( circle ),
		  circle->center.x, circle->center.y,
		  circle->radius
		  );
  if ( r < 0 )
    s = NULL;

  return s;
}


/**
 * Draw pixel in every octant, be carefull to do not paint outside the screen
 */
static inline void
draw_pixel_all_octants( uint16_t x0, uint16_t y0, uint16_t ox, uint16_t oy,
			surface_t *surface, color_t color, float transp )
{
  surface_draw_pixel( surface, x0 + ox, y0 + oy, color, transp );
  surface_draw_pixel( surface, x0 + ox, y0 - oy, color, transp );
  surface_draw_pixel( surface, x0 - ox, y0 + oy, color, transp );
  surface_draw_pixel( surface, x0 - ox, y0 - oy, color, transp );

  surface_draw_pixel( surface, x0 + oy, y0 + ox, color, transp );
  surface_draw_pixel( surface, x0 + oy, y0 - ox, color, transp );
  surface_draw_pixel( surface, x0 - oy, y0 + ox, color, transp );
  surface_draw_pixel( surface, x0 - oy, y0 - ox, color, transp );
}

static inline void
draw_pixel_all_octants_antialias_out( uint16_t x0, uint16_t y0,
				      uint16_t ox, float oy,
				      surface_t *surface, color_t color,
				      float transp )
{
  transp = transp + 1 - ( oy - trunc( oy ) );
  oy = trunc( oy ) + 1;

  surface_draw_pixel( surface, x0 + ox, y0 + oy, color, transp );

  surface_draw_pixel( surface, x0 + ox, y0 - oy, color, transp );

  surface_draw_pixel( surface, x0 - ox, y0 + oy, color, transp );

  surface_draw_pixel( surface, x0 - ox, y0 - oy, color, transp );

  surface_draw_pixel( surface, x0 + oy, y0 + ox, color, transp );

  surface_draw_pixel( surface, x0 + oy, y0 - ox, color, transp );

  surface_draw_pixel( surface, x0 - oy, y0 + ox, color, transp );

  surface_draw_pixel( surface, x0 - oy, y0 - ox, color, transp );
}

static inline void
draw_pixel_all_octants_antialias_in( uint16_t x0, uint16_t y0,
				     uint16_t ox, float oy,
				     surface_t *surface, color_t color,
				     float transp )
{
  transp = transp + ( oy - trunc( oy ) );
  oy = trunc( oy );

  surface_draw_pixel( surface, x0 + ox, y0 + oy, color, transp );

  surface_draw_pixel( surface, x0 + ox, y0 - oy, color, transp );

  surface_draw_pixel( surface, x0 - ox, y0 + oy, color, transp );

  surface_draw_pixel( surface, x0 - ox, y0 - oy, color, transp );

  surface_draw_pixel( surface, x0 + oy, y0 + ox, color, transp );

  surface_draw_pixel( surface, x0 + oy, y0 - ox, color, transp );

  surface_draw_pixel( surface, x0 - oy, y0 + ox, color, transp );

  surface_draw_pixel( surface, x0 - oy, y0 - ox, color, transp );
}


static inline void
fill_circle_segments( uint16_t x0, uint16_t y0, uint16_t ox, uint16_t oy,
		      surface_t *surface, color_t color, float transp,
		      uint16_t last_ox, uint16_t last_oy )
{
  /* avoid numbers < 0 (which will go wild since types are unsigned!) */
  uint16_t nxox = min( ox, x0 );
  uint16_t nxoy = min( oy, x0 );

  uint16_t nyoy = min( oy, y0 );
  uint16_t nyox = min( ox, y0 );

  if ( last_oy != oy )
    {
      line_draw_horizontal( x0 - nxox, x0 + ox, y0 - nyoy,
			    surface, color, transp);
      line_draw_horizontal( x0 - nxox, x0 + ox, y0 + oy,
			    surface, color, transp);
    }

  if ( ( last_ox != ox ) && ( ox != oy ) )
    {
      if ( ox != 0 ) /* don't paint middle line twice! */
	line_draw_horizontal( x0 - nxoy, x0 + oy, y0 - nyox,
			      surface, color, transp);

      line_draw_horizontal( x0 - nxoy, x0 + oy, y0 + ox,
			    surface, color, transp);
    }
}

void
circle_draw_trivial( uint16_t x, uint16_t y, uint16_t radius,
		     surface_t *surface, color_t color,
		     float transp, int fill )
{
  assert( surface != NULL );
  assert( surface->buffer != NULL );
  float ox, oy, end_x;

  end_x = ceil( (float)radius / sqrt(2) );

  uint16_t last_ox=-1, last_oy=-1;

  for ( ox=0; ox <= end_x; ox++ )
    {
      oy = sqrt( ( radius * radius ) - ( ox * ox ) );

      if ( fill )
	{
	  fill_circle_segments( x, y, ox, oy, surface, color, transp,
				last_ox, last_oy );
	  last_ox = ox;
	  last_oy = oy;
	}
      else
	draw_pixel_all_octants( x, y, ox, oy, surface, color, transp );
    }
}

void
circle_draw_trivial_antialias( uint16_t x, uint16_t y, uint16_t radius,
			       surface_t *surface, color_t color,
			       float transp, int fill )
{
  assert( surface != NULL );
  assert( surface->buffer != NULL );
  float ox, oy, end_x;

  end_x = ceil( (float)radius / sqrt(2) );

  uint16_t last_ox=-1, last_oy=-1;

  for ( ox=0; ox <= end_x; ox++ )
    {
      oy = sqrt( ( radius * radius ) - ( ox * ox ) );

      draw_pixel_all_octants_antialias_out( x, y, ox, oy,
					    surface, color, transp );
      if ( fill )
	{
	  fill_circle_segments( x, y, ox, oy, surface, color, transp,
				last_ox, last_oy );
	  /* paint pixels at the edge in full color */
	  draw_pixel_all_octants( x, y, ox, oy, surface, color, transp );
	  last_ox = ox;
	  last_oy = oy;
	}
      else
	draw_pixel_all_octants_antialias_in( x, y, ox, oy,
					     surface, color, transp );
    }
}

/**
 * Internal function to draw circles using the Bresenham (integer only)
 * algorithm.
 */
void
circle_draw_bresenham( uint16_t x, uint16_t y, uint16_t radius,
		       surface_t *surface, color_t color,
		       float transp, int fill )
{
  assert( surface != NULL );
  assert( surface->buffer != NULL );

  int d = 1 - radius;
  uint16_t oy = radius;
  uint16_t ox;

  uint16_t last_oy = -1;
  uint16_t last_ox = -1;

  for ( ox=0; ox <= oy; ox++ )
    {
      if ( fill )
	{
	  fill_circle_segments( x, y, ox, oy, surface, color, transp,
				last_ox, last_oy);
	  last_ox = ox;
	  last_oy = oy;
	}
      else
	draw_pixel_all_octants( x, y, ox, oy, surface, color, transp );

      d += ox * 2 + 3;
      if ( d > 0 )
	{
	  d += 2 - 2 * oy;
	  oy --;
	}
    }
}

void (*circle_draw_aliased)( uint16_t, uint16_t, uint16_t, surface_t *,
			     color_t, float, int ) =
  circle_draw_bresenham;

void (*circle_draw_antialiased)( uint16_t, uint16_t, uint16_t, surface_t *,
				 color_t, float, int ) =
  circle_draw_trivial_antialias;


/**
 * Generic circle drawing algoritm.
 *
 * You may toggle drawing using circle_draw_aliased and circle_draw_antialiased
 * variables.
 *
 * @param circle the circle object to draw.
 * @param surface where to draw the object.
 */
void
__circle_draw( circle_t *circle, surface_t *surface )
{
  assert( surface != NULL );
  assert( surface->buffer != NULL );

  color_t  color  = OBJECT_COLOR( circle );
  float transp    = OBJECT_TRANSP( circle );
  uint16_t radius = circle->radius;
  coord_t center  = circle->center;

  if ( radius == 0 )
    {
      surface_draw_pixel( surface, center.x, center.y, color, transp );
      return;
    }


  void (*draw)( uint16_t, uint16_t, uint16_t, surface_t *, color_t,
		float, int ) = NULL;

  int fill = OBJECT_DRAW_OPTS( circle ) & DRAW_FILL;

  if ( OBJECT_DRAW_OPTS( circle ) & DRAW_ANTIALIAS )
    draw = circle_draw_antialiased;
  else
    draw = circle_draw_aliased;

  draw( center.x, center.y, radius, surface, color, transp, fill );
}

void
circle_draw( circle_t *circle, surface_t *surface )
{
  surface_reset_visits( surface );
  __circle_draw( circle, surface );
}
